import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;


public abstract class Gate {

	private String name;
	private boolean outputValue;
	private LinkedList<Gate> inputGates = new LinkedList<Gate>();
	private LinkedList<Gate> outputGates = new LinkedList<Gate>();
	
	private static int delay;
	
	public static void setDelay(int i) {
		delay = i;
	}
	
	public static int getDelay() {
		return delay;
	}
	
	public static Map<String,Gate> createGates(File f) {
		int rowNr = 0;
				
		try {
			
			BufferedReader configFile = new BufferedReader(new FileReader(f));
			LinkedHashMap<String,Gate> avbildningsTabell = new LinkedHashMap<String,Gate>();
			
			//First go, create hash map.
			while(true) {
				
				//Read a new line from the file
				String line = configFile.readLine();
				rowNr++;
				
				//Reached the end of the file? Yes: We're done, move along, no more to see.
				if(line == null)
					break;
				
				Scanner directive = new Scanner(line);
				
				if(directive.hasNext()) {
					String name = directive.next();
					
					//Is the row out commanted? Yes: Skip it. No: Add it to our hash map.
	
					if(name.charAt(0) != '*' && name.charAt(0) != '/') {				
						
						//Unique name? Yes: Continue. No: Exception on line X
						if(!avbildningsTabell.containsKey(name)) {						
							if(directive.hasNext()) {
								String rawName = directive.next();
								//Adjust the name to "XxxxGate"
								String className = rawName.substring(0,1).toUpperCase() + rawName.substring(1).toLowerCase() + "Gate";
								
								//All is well in our world, add the name and an instance of the class in the map.
								Gate newGate = (Gate)Class.forName(className).newInstance();
								newGate.setName(name);
								avbildningsTabell.put(name, newGate );
							}
						}
						else 
							throw new GateException("Error on line " + rowNr + " in file " + f.getName() + ". Can't use that name again.");
					}
										
				}					
				
			}
			
			//Second go, connect gates to each other
			
			//Reopen file
			configFile = new BufferedReader(new FileReader(f));
			rowNr = 0;
						
			while(true) {
				
				//Read a new line from the file
				String line = configFile.readLine();
				rowNr++;
				
				//Reached the end of the file? Yes: We're done, move along, no more to see.
				if(line == null)
					break;
				
				//Is the row out commanted? Yes: Skip it. No: Add it to our hash map.
				try {
					if(line.charAt(0) != '*' && line.charAt(0) != '/') { 
						Scanner directive = new Scanner(line);
						
						//Get the gate obj on this line
						Gate g = avbildningsTabell.get(directive.next());
						
						//Throw away what we already know. (the gates class name)
						directive.next();
						
						//Are we suppose to connect the gate to anything? Yes: Do it. 
						while(directive.hasNext()) { 
							String key = directive.next();
							//Name valid?
							if(avbildningsTabell.containsKey(key))
								g.setInputGate(avbildningsTabell.get(key));
							else
								throw new GateException(key + " on line " + rowNr +" is not a gate in my world...");
						}
					}			
				}
				catch(StringIndexOutOfBoundsException e) { /*The line is empty*/ }
			}
			
			return (Map<String,Gate>)avbildningsTabell;
			
		}
		catch(FileNotFoundException e) {
			throw new GateException("Can't find the file: " + f.getName());
		}
		catch(ClassNotFoundException e) {
			throw new GateException("Error on line " + rowNr + " in file " + f.getName() + ". Class cannot be found.");
		}
		catch(IllegalAccessException e) {
			throw new GateException("Error on line " + rowNr + " in file " + f.getName());
		}
		catch(InstantiationException e) {
			throw new GateException("Error on line " + rowNr + " in file " + f.getName() + ". Can't instansiate that class");
		}
		catch(IOException e) { e.printStackTrace();}
		
		//Something went terribly wrong!
		return null;
	
		
	}
	
	
	
	public void init() {
		outputChanged(calculateValue());
	}
	
	public String getName() {
		return name;
	}
	
	public void setName(String s) {
		name = s;
	}
	
	public boolean getOutputValue() {
		return outputValue;
	}
	
	public void setInputGate(Gate g) {
		inputGates.add(g);
		g.outputGates.add(this);
	}
	
	public List<Gate> getInputGates() {
		return new LinkedList<Gate>(inputGates);
	}
	
	public abstract boolean calculateValue();
	
	public abstract void inputChanged();
	
	public void outputChanged(boolean b) {
		outputValue = b;
		for(Gate g : outputGates)
			g.inputChanged();
	}
	
}
